// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Author: Markus Gutschke

#ifdef USE_AXTLS

#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include "check.h"
#include "ssl.h"

#define min(a,b) ({ typeof(a) _a=(a); typeof(b) _b=(b); _a < _b ? _a : _b; })


struct SSLHandle {
  SSL     *ssl;
  char    *remainder;
  ssize_t rc;
};


struct SSL *newSSL(void) {
  struct SSL *ssl;
  check(ssl = malloc(sizeof(struct SSL)));
  initSSL(ssl);
  return ssl;
}

void initSSL(struct SSL *ssl) {
  check(ssl->ssl_ctx = ssl_ctx_new(SSL_SERVER_VERIFY_LATER,
                                   SSL_DEFAULT_CLNT_SESS));
}

void destroySSL(struct SSL *ssl) {
  if (ssl) {
    ssl_ctx_free(ssl->ssl_ctx);
  }
}

void deleteSSL(struct SSL *ssl) {
  destroySSL(ssl);
  free(ssl);
}

struct SSLHandle *openSSLHandle(struct SSL *ssl, int fd) {
  struct SSLHandle *handle;
  check(handle      = malloc(sizeof(struct SSLHandle)));
  handle->remainder = NULL;
  handle->rc        = 0;
  check(handle->ssl = ssl_client_new(ssl->ssl_ctx, fd, NULL, 0));
  check(ssl_handshake_status(handle->ssl) == SSL_OK);
  return handle;
}

void closeSSLHandle(struct SSLHandle *handle) {
  ssl_free(handle->ssl);
  free(handle);
}

ssize_t SSLread(struct SSLHandle *handle, char *buf, size_t sz) {
  ssize_t avail            = sz;
  while (avail > 0) {
    char *dat;
    ssize_t rc;
    if (handle->rc) {
      dat                  = handle->remainder;
      rc                   = handle->rc;
      handle->remainder    = NULL;
      handle->rc           = 0;
    } else {
      dat                  = NULL;
      rc                   = ssl_read(handle->ssl, (uint8_t **)&dat);
    }
    if (rc > 0) {
      memcpy(buf, dat, min(avail, rc));
      if (rc > avail) {
        handle->remainder  = dat + avail;
        handle->rc         = rc - avail;
        buf               += avail;
        avail              = 0;
      } else {
        avail             -= rc;
        buf               += rc;
      }
    } else if (avail < sz) {
      handle->remainder    = NULL;
      handle->rc           = rc;
      break;
    } else if (rc) {
      handle->remainder    = NULL;
      handle->rc           = rc;
      if (rc == SSL_ERROR_DEAD || rc == SSL_CLOSE_NOTIFY ||
          rc == SSL_ERROR_CONN_LOST) {
        return 0;
      } else {
        errno              = EIO;
        return rc;
      }
    } else {
      errno                = EAGAIN;
      return -1;
    }
  }
  return sz - avail;
}

ssize_t SSLwrite(struct SSLHandle *handle, const char *buf, size_t sz) {
  ssize_t rc = ssl_write(handle->ssl, (uint8_t *)buf, sz);
  if (rc < 0) {
    errno = EIO;
    return -1;
  }
  return rc;
}

#endif  // USE_AXTLS
